/*
 * Copyright (C) 2009 M. Hautle.
 * 
 * This file is part of TrackedBean.
 * 
 * TrackedBean is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * TrackedBean is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with TrackedBean. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.trackedbean.binding.components;

import java.awt.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.text.*;
import ch.trackedbean.binding.action.*;
import ch.trackedbean.binding.components.renderers.*;
import ch.trackedbean.binding.mapping.*;
import ch.trackedbean.loaders.*;
import ch.trackedbean.loaders.lists.*;
import ch.trackedbean.tracking.*;

/**
 * Datapanel with a mapping and a selection based popup.<br>
 * Use {@link #getPopupManager()} to configure the popup.<br>
 * Use {@link #getMapping()} for special mappings (so those not covered by the add methods).
 * 
 * @author M. Hautle
 * @param <T> The model type
 */
public class DataPanel<T> extends JPanel {
    /** The mapping */
    private final MappingGroup<T> mapping;

    /** The popup manager. */
    private final LocalPopupManager popupManager = new LocalPopupManager();

    /** The panel to be populated by the add methods. <code>null</code> means this panel. */
    private JPanel ref = null;

    /**
     * Default constructor.
     * 
     * @param readOnly True if the binding should be read only.
     */
    public DataPanel(boolean readOnly) {
        mapping = new MappingGroup<T>(readOnly);
        super.setComponentPopupMenu(new JPopupMenu());
    }

    /**
     * Default constructor.
     * 
     * @param layout The layoutmanager to use
     * @param readOnly True if the binding should be read only.
     */
    public DataPanel(LayoutManager layout, boolean readOnly) {
        super(layout);
        mapping = new MappingGroup<T>(readOnly);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public JPopupMenu getComponentPopupMenu() {
        final T model = getModel();
        if (model == null)
            return null;
        final JPopupMenu menu = popupManager.getMenu(Collections.singletonList(model));
        return menu.getComponentCount() > 0 ? menu : null;
    }

    /**
     * This panel has it's own popup, so this method returns <code>false</code>
     * 
     * @return false
     */
    @Override
    public boolean getInheritsPopupMenu() {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setComponentPopupMenu(JPopupMenu popup) {
        throw new UnsupportedOperationException("This method is not supported!");
    }

    /**
     * Sets the panel to be populated by the add calls.<br>
     * You may use this method if you need sub panels only for layouting purposes.
     * 
     * @param p The panel or null for this panel
     */
    public void setReferencePanel(JPanel p) {
        ref = p;
    }

    /**
     * Creates a new panel with the given title, adds it to the current/root panel and sets it as new reference panel (by calling
     * {@link #setReferencePanel(JPanel)}).
     * 
     * @param title The title
     * @param layout The layout of the new panel
     * @param rootPanel True if the panel should be added on this panel rather than on the current reference panel (set by {@link #setReferencePanel(JPanel)}).
     * @param constraints The layout constraints to use when adding the newly created panel to it's parent
     * @return The newly created panel
     */
    public JPanel addTitlePanel(String title, LayoutManager layout, boolean rootPanel, Object constraints) {
        final JPanel p = new JPanel(layout);
        p.setBorder(new TitledBorder(title));
        if (rootPanel)
            setReferencePanel(null);
        add(p, constraints);
        setReferencePanel(p);
        return p;
    }

    /**
     * Adds the given label to the panel and registers it on the given property.
     * 
     * @param path The path to the property to map
     * @param label The label to add
     * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
     */
    public BindingInfos add(String path, JLabel label) {
        return add(path, label, null);
    }

    /**
     * Adds the given label to the panel and registers it on the given property.
     * 
     * @param path The path to the property to map
     * @param label The label to add
     * @param constraints The layout constraints
     * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
     */
    public BindingInfos add(String path, JLabel label, Object constraints) {
        add(label, constraints);
        return mapping.createBinding(path, label);
    }

    /**
     * Adds the given component to the panel and registers it on the given properties status.
     * 
     * @param <C> The element definition
     * @param path The path to the property to map
     * @param element The element to add
     */
    public <C extends Component & IStatusDependent> void addStatusElement(String path, C element) {
        addStatusElement(path, element, null);
    }

    /**
     * Adds the given component to the panel and registers it on the given properties status.
     * 
     * @param <C> The element definition
     * @param path The path to the property to map
     * @param element The element to add
     * @param constraints The layout constraints
     */
    public <C extends Component & IStatusDependent> void addStatusElement(String path, C element, Object constraints) {
        add(element, constraints);
        mapping.createStatusDependentBinding(path, element);
    }

    /**
     * Adds the given textcomponent to the panel and registers it on the given property.
     * 
     * @param path The path to the property to map
     * @param text The textcomponent to add
     * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
     */
    public BindingInfos add(String path, JTextComponent text) {
        return add(path, text, null);
    }

    /**
     * Adds the given textcomponent to the panel and registers it on the given property.
     * 
     * @param path The path to the property to map
     * @param text The textcomponent to add
     * @param constraints The layout constraints
     * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
     */
    public BindingInfos add(String path, JTextComponent text, Object constraints) {
        add(text, constraints);
        return mapping.createBinding(path, text);
    }

    /**
     * Adds the given combobox to the panel and registers it on the given property.<br>
     * Consider using {@link #add(String, JComboBox, Class, Object, Map)}.
     * 
     * @param path The path to the property to map
     * @param combobox The combobox to add
     * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
     */
    public BindingInfos add(String path, JComboBox combobox) {
        return add(path, combobox, null);
    }

    /**
     * Adds the given combobox to the panel and registers it on the given property.<br>
     * Consider using {@link #add(String, JComboBox, Class, Object, Map, Object)}.
     * 
     * @param path The path to the property to map
     * @param combobox The combobox to add
     * @param constraints The layout constraints
     * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
     */
    public BindingInfos add(String path, JComboBox combobox, Object constraints) {
        add(combobox, constraints);
        return mapping.createBinding(path, combobox);
    }

    /**
     * Adds the given combobox to the panel and registers it on the given property.<br>
     * The combobox content list will be loaded using {@link DataManager#getList(Class, String, Map)}.
     * 
     * @param path The path to the property to map
     * @param combobox The combobox to add
     * @param type The content type of the combobox list
     * @param subType Additional discriminator specifying the "subtype" of the combobox content list (may be null)
     * @param settings Additional settings for the combobox content load (may be null)
     * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
     */
    public BindingInfos add(String path, JComboBox combobox, Class<?> type, Object subType, Map settings) {
        return add(path, combobox, type, subType, settings, null);
    }

    /**
     * Adds the given combobox to the panel and registers it on the given property.<br>
     * The combobox content list will be loaded using {@link DataManager#getList(Class, String, Map)}.
     * 
     * @param path The path to the property to map
     * @param combobox The combobox to add
     * @param type The content type of the combobox list
     * @param subType Additional discriminator specifying the "subtype" of the combobox content list (may be null)
     * @param settings Additional settings for the combobox content load (may be null)
     * @param constraints The layout constraints
     * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
     */
    public BindingInfos add(String path, JComboBox combobox, Class<?> type, Object subType, Map settings, Object constraints) {
        add(combobox, constraints);
        return mapping.createBinding(path, combobox, type, subType, settings);
    }

    /**
     * Adds the given combobox to the panel and registers it on the given property.<br>
     * The passed el string is used to extract the display values out of the provided list objects.<br>
     * This call sets simply an {@link SimpleELListRenderer} on the passed combobox.
     * 
     * @param path The path to the property to map
     * @param el An string containing el expression escaped in <code>${el}</code> blocks
     * @param combobox The {@link JComboBox} to link
     * @param constraints The layout constraints
     * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
     * @throws IllegalStateException if the group is already bound
     */
    public BindingInfos add(final String path, final String el, final JComboBox combobox, Object constraints) {
        combobox.setRenderer(new SimpleELListRenderer(el, ""));
        add(combobox, constraints);
        return mapping.createBinding(path, combobox);
    }

    /**
     * Adds the given combobox to the panel and registers it on the given property.<br>
     * The combobox content list will be loaded using {@link DataManager#getList(Class, String, Map)}.<br>
     * The passed el string is used to extract the display values out of the provided list objects.<br>
     * This call sets simply an {@link SimpleELListRenderer} on the passed combobox.
     * 
     * @param path The path to the property to map
     * @param el An string containing el expression escaped in <code>${el}</code> blocks
     * @param combobox The {@link JComboBox} to link
     * @param type The content type of the combobox list
     * @param subType Additional discriminator specifying the "subtype" of the combobox content list (may be null)
     * @param settings Additional settings for the combobox content load (may be null)
     * @param constraints The layout constraints
     * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
     * @see DataManager#getList(Class, String, Map)
     * @see IListLoader
     * @throws IllegalStateException if the group is already bound
     */
    public BindingInfos add(final String path, final String el, final JComboBox combobox, Class<?> type, Object subType, Map settings, Object constraints) {
        combobox.setRenderer(new SimpleELListRenderer(el, ""));
        add(combobox, constraints);
        return mapping.createBinding(path, combobox, type, subType, settings);
    }

    /**
     * Adds the given combobox to the panel and registers it on the given property.<br>
     * The passed el string is used to extract the display values out of the provided list objects.<br>
     * This call sets simply an {@link SimpleELListRenderer} on the passed combobox.
     * 
     * @param path The path to the property to map
     * @param el An string containing el expression escaped in <code>${el}</code> blocks
     * @param combobox The {@link JComboBox} to link
     * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
     * @throws IllegalStateException if the group is already bound
     */
    public BindingInfos add(final String path, final String el, final JComboBox combobox) {
        return add(path, el, combobox, null);
    }

    /**
     * Adds the given combobox to the panel and registers it on the given property.<br>
     * The combobox content list will be loaded using {@link DataManager#getList(Class, String, Map)}.<br>
     * The passed el string is used to extract the display values out of the provided list objects.<br>
     * This call sets simply an {@link SimpleELListRenderer} on the passed combobox.
     * 
     * @param path The path to the property to map
     * @param el An string containing el expression escaped in <code>${el}</code> blocks
     * @param combobox The {@link JComboBox} to link
     * @param type The content type of the combobox list
     * @param subType Additional string specifying the "subtype" of the combobox content list (may be null)
     * @param settings Additional settings for the combobox content load (may be null)
     * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
     * @see DataManager#getList(Class, String, Map)
     * @see IListLoader
     * @throws IllegalStateException if the group is already bound
     */
    public BindingInfos add(final String path, final String el, final JComboBox combobox, Class<?> type, Object subType, Map settings) {
        return add(path, el, combobox, type, subType, settings, null);
    }

    /**
     * Adds the given list to the panel and registers it on the given property.
     * 
     * @param path The path to the property to map
     * @param list The list to map
     * @param scrollPane The {@link JScrollPane} holding the list - only the pane will be added to the panel
     * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
     */
    public BindingInfos add(String path, JList list, JScrollPane scrollPane) {
        return add(path, list, scrollPane, null);
    }

    /**
     * Adds the given list to the panel and registers it on the given property.
     * 
     * @param path The path to the property to map
     * @param list The list to map
     * @param scrollPane The {@link JScrollPane} holding the list - only the pane will be added to the panel
     * @param constraints The layout constraints
     * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
     */
    public BindingInfos add(String path, JList list, JScrollPane scrollPane, Object constraints) {
        return add(path, null, list, scrollPane, null);
    }

    /**
     * Adds the given list to the panel and registers it on the given property.<br>
     * The passed el string is used to extract the display values out of the provided list objects.<br>
     * 
     * @param path The path to the property to map
     * @param el An string containing el expression escaped in <code>${el}</code> blocks
     * @param list The list to map
     * @param scrollPane The {@link JScrollPane} holding the list - only the pane will be added to the panel
     * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
     */
    public BindingInfos add(String path, String el, JList list, JScrollPane scrollPane) {
        return add(path, el, list, scrollPane, null);
    }

    /**
     * Adds the given list to the panel and registers it on the given property.<br>
     * The passed el string is used to extract the display values out of the provided list objects.<br>
     * 
     * @param path The path to the property to map
     * @param el An string containing el expression escaped in <code>${el}</code> blocks
     * @param list The list to map
     * @param scrollPane The {@link JScrollPane} holding the list - only the pane will be added to the panel
     * @param constraints The layout constraints
     * @return The {@link BindingInfos} object for further configuration of the binding (they will be activated when {@link #setModel(Object)} is called)
     */
    public BindingInfos add(String path, String el, JList list, JScrollPane scrollPane, Object constraints) {
        add(scrollPane, constraints);
        return mapping.createBinding(path, list, el);
    }

    /**
     * @return Returns the popupManager.
     */
    public LocalPopupManager getPopupManager() {
        return popupManager;
    }

    /**
     * Returns the model.
     * 
     * @return The model
     * @see ch.trackedbean.binding.mapping.MappingGroup#getModel()
     */
    public T getModel() {
        return mapping.getModel();
    }

    /**
     * Sets the given model.
     * 
     * @param model The model to set or null to reset the model
     * @see ch.trackedbean.binding.mapping.MappingGroup#setModel(Object)
     */
    public void setModel(T model) {
        mapping.setModel(model);
    }

    /**
     * Returns the model status.
     * 
     * @return The model status
     * @see ch.trackedbean.binding.mapping.MappingGroup#getModelStatus()
     */
    public IBeanStatus getModelStatus() {
        return mapping.getModelStatus();
    }

    /**
     * Returns the mapping from the panel.
     * 
     * @return Returns the mapping.
     */
    public MappingGroup<T> getMapping() {
        return mapping;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Component add(Component comp) {
        if (ref == null)
            return super.add(comp);
        return ref.add(comp);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Component add(String name, Component comp) {
        if (ref == null)
            return super.add(name, comp);
        return ref.add(name, comp);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Component add(Component comp, int index) {
        if (ref == null)
            return super.add(comp, index);
        return ref.add(comp, index);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void add(Component comp, Object constraints) {
        if (ref == null)
            super.add(comp, constraints);
        else
            ref.add(comp, constraints);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void add(Component comp, Object constraints, int index) {
        if (ref == null)
            super.add(comp, constraints, index);
        else
            ref.add(comp, constraints, index);
    }
}
